﻿// ===============================================================================
//  Copyright © ymind.net. All rights reserved .
//  官方网站：http://ymind.net/
//  版权所有：彦铭工作室 老陈出品
// ===============================================================================

using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Xml;
using Lcsoft.UrlRewriter.Errors;
using Lcsoft.UrlRewriter.Logging;
using Lcsoft.UrlRewriter.Parsers;
using Lcsoft.UrlRewriter.Transforms;
using Lcsoft.UrlRewriter.Utilities;

namespace Lcsoft.UrlRewriter.Configuration
{
	/// <summary>
	/// 提供读取地址重写配置信息的功能。
	/// </summary>
	public sealed class RewriterConfigurationReader
	{
		/// <summary>
		/// 读取地址重写配置信息。
		/// </summary>
		/// <param name="section">包含地址重写配置信息的 <see cref="XmlNode"/>。</param>
		/// <returns><see cref="RewriterConfiguration"/> 对象。</returns>
		public static RewriterConfiguration Read(XmlNode section)
		{
			if (section == null) throw new ArgumentNullException("section");

			var config = RewriterConfiguration.Create();

			foreach (XmlNode node in section.ChildNodes)
			{
				if (node.NodeType != XmlNodeType.Element) continue;

				switch (node.LocalName)
				{
					case Constants.ElementErrorHandler:
						_ReadErrorHandler(node, config);

						break;
					case Constants.ElementDefaultDocuments:
						_ReadDefaultDocuments(node, config);

						break;
					case Constants.ElementRegister:
						if (node.Attributes[Constants.AttrParser] != null) _ReadRegisterParser(node, config);
						else if (node.Attributes[Constants.AttrTransform] != null) _ReadRegisterTransform(node, config);
						else if (node.Attributes[Constants.AttrLogger] != null) _ReadRegisterLogger(node, config);

						break;
					case Constants.ElementMapping:
						_ReadMapping(node, config);

						break;
					default:
						_ReadRule(node, config);

						break;
				}
			}

			return config;
		}

		private static void _ReadRegisterTransform(XmlNode node, RewriterConfiguration config)
		{
			var typeNode = node.Attributes[Constants.AttrTransform];

			if (typeNode == null) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.AttributeRequired, Constants.AttrTransform), node);
			if (node.ChildNodes.Count > 0) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.ElementNoElements, Constants.ElementRegister), node);

			if (config == null) throw new ArgumentNullException("config");

			var handler = TypeHelper.Activate<IRewriteTransform>(typeNode.Value, null);

			if (handler != null) config.Transforms.AddTransform(handler);
		}

		private static void _ReadRegisterLogger(XmlNode node, RewriterConfiguration config)
		{
			var typeNode = node.Attributes[Constants.AttrLogger];

			if (typeNode == null) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.AttributeRequired, Constants.AttrLogger), node);
			if (node.ChildNodes.Count > 0) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.ElementNoElements, Constants.ElementRegister), node);

			if (config == null) throw new ArgumentNullException("config");

			var logger = TypeHelper.Activate<IRewriteLogger>(typeNode.Value, null);

			if (logger != null) config.Logger = logger;
		}

		private static void _ReadRegisterParser(XmlNode node, RewriterConfiguration config)
		{
			var typeNode = node.Attributes[Constants.AttrParser];

			if (typeNode == null) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.AttributeRequired, Constants.AttrParser), node);
			if (node.ChildNodes.Count > 0) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.ElementNoElements, Constants.ElementRegister), node);

			if (config == null) throw new ArgumentNullException("config");

			var parser = TypeHelper.Activate(typeNode.Value, null);
			var actionParser = parser as IRewriteActionParser;

			if (actionParser != null) config.ActionParsers.AddParser(actionParser);

			var conditionParser = parser as IRewriteConditionParser;

			if (conditionParser != null) config.ConditionParsers.AddParser(conditionParser);
		}

		private static void _ReadDefaultDocuments(XmlNode node, RewriterConfiguration config)
		{
			if (node == null) throw new ArgumentNullException("node");

			if (config == null) throw new ArgumentNullException("config");

			foreach (XmlNode childNode in node.ChildNodes) if (childNode.NodeType == XmlNodeType.Element && childNode.LocalName == Constants.ElementDocument) config.DefaultDocuments.Add(childNode.InnerText);
		}

		private static void _ReadErrorHandler(XmlNode node, RewriterConfiguration config)
		{
			if (node == null) throw new ArgumentNullException("node");
			if (node.Attributes == null) throw new ArgumentOutOfRangeException("node");

			if (config == null) throw new ArgumentNullException("config");

			var codeNode = node.Attributes[Constants.AttrCode];

			if (codeNode == null) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.AttributeRequired, Constants.AttrCode), node);

			var typeNode = node.Attributes[Constants.AttrType];
			var urlNode = node.Attributes[Constants.AttrUrl];

			if (typeNode == null && urlNode == null) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.AttributeRequired, Constants.AttrUrl), node);

			IRewriteErrorHandler handler;

			if (typeNode == null) handler = new DefaultErrorHandler(urlNode.Value);
			else
			{
				// <error-handler code="500" url="/oops.aspx" />
				handler = TypeHelper.Activate<IRewriteErrorHandler>(typeNode.Value, null);

				if (handler == null) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.InvalidTypeSpecified));
			}

			config.ErrorHandlers.Add(Convert.ToInt32(codeNode.Value), handler);
		}

		private static void _ReadMapping(XmlNode node, RewriterConfiguration config)
		{
			if (node == null) throw new ArgumentNullException("node");
			if (node.Attributes == null) throw new ArgumentOutOfRangeException("node");

			if (config == null) throw new ArgumentNullException("config");

			var nameNode = node.Attributes[Constants.AttrName];
			var map = new StringDictionary();

			foreach (XmlNode mapNode in node.ChildNodes)
			{
				if (mapNode.NodeType != XmlNodeType.Element) continue;
				if (mapNode.LocalName != Constants.ElementMap) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.ElementNotAllowed, mapNode.LocalName), node);

				var fromValueNode = mapNode.Attributes[Constants.AttrFrom];

				if (fromValueNode == null) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.AttributeRequired, Constants.AttrFrom), node);

				var toValueNode = mapNode.Attributes[Constants.AttrTo];

				if (toValueNode == null) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.AttributeRequired, Constants.AttrTo), node);

				map.Add(fromValueNode.Value, toValueNode.Value);
			}

			config.Transforms.AddTransform(new StaticMappingTransform(nameNode.Value, map));
		}

		private static void _ReadRule(XmlNode node, RewriterConfiguration config)
		{
			if (node == null) throw new ArgumentNullException("node");
			if (node.Attributes == null) throw new ArgumentOutOfRangeException("node");

			if (config == null) throw new ArgumentNullException("config");

			var parsed = false;
			var parsers = config.ActionParsers.GetParsers(node.LocalName);

			if (parsers != null)
			{
				foreach (IRewriteActionParser parser in parsers)
				{
					if (!parser.AllowsNestedActions && node.ChildNodes.Count > 0) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.ElementNoElements, parser.Name), node);
					if (!parser.AllowsAttributes && node.Attributes.Count > 0) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.ElementNoAttributes, parser.Name), node);

					var rule = parser.Parse(node, config);

					if (rule == null) continue;

					config.Rules.Add(rule);
					parsed = true;

					break;
				}
			}

			if (!parsed) throw new ConfigurationErrorsException(MessageProvider.FormatString(Message.ElementNotAllowed, node.LocalName), node);
		}
	}
}
